/*  -*- mode: c++; coding: utf-8; c-file-style: "stroustrup"; -*-

    Copyright 2008 Asier Aguirre <asier.aguirre@gmail.com>
    This file is part of Intromit.

    Intromit is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Intromit is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Intromit.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "render_obj_frame.hh"
#include "render_params.hh"
#include "render_stats.hh"
#include "event_mng.hh"
#include "ogl.hh"
#include "ogl_font.hh"
#include "key_names.hh"

// DEFINES
#define CLEAR_COLOR_NONSELECT     0.08, 0.08, 0.16, 0.5
#define CLEAR_COLOR_SELECT        0.10, 0.10, 0.20, 0.5
#define CLEAR_COLOR_CSELECT       0.12, 0.12, 0.24, 0.5
#define CLEAR_COLOR_ANIM_USEC     150000

#define STATS_COLOR_TITLE         0.90, 0.60, 0.20, 0.5
#define STATS_COLOR_VALUE         0.50, 0.40, 0.10, 0.5
#define STATS_COLOR_BOX           0.00, 0.00, 0.00, 0.2

#define STATS_BOX_NLINES          6
#define STATS_BOX_NCOLS           16
#define STATS_BOX_NCOLS2          7
#define STATS_BOX_MARGIN          2 // pixel
#define STATS_BOX_TITLE_MARGIN    6 // pixel
#define STATS_BOX_YOFF            1 // pixel
#define STATS_BOX_NEG_MARGIN     -1e5 // if less than this in a box coord position, justify to the other side

#define HELP_KEY_COLOR            1.00, 1.00, 1.00, 0.5
#define HELP_TIP_COLOR            0.80, 1.00, 0.70, 0.5
#define HELP_COLOR_BOX            0.00, 0.30, 0.00, 0.3
#define HELP_BOX_MARGX            3.5
#define HELP_BOX_MARGY            4.5
#define INC_HELP_STR              32

#define CURSOR_COLOR              1.00, 1.00, 1.00, 0.3
#define CENTER_CURSOR_SIZE        10.0

#define SEL_FRAME_STATS           IS_SEL_USER(0x01)

#define MAX_TIP_ENTRIES           32

CLASS_INIT(render_obj_frame); // RENDER_OBJ_FRAME CLASS

render_obj_frame::render_obj_frame()
{
    CLASS_CTOR(render_obj_frame);
    ctor();
}

render_obj_frame::~render_obj_frame()
{
    CLASS_DTOR(render_obj_frame);
    dtor();
}

void render_obj_frame::ctor()
{
    // modelview
    modelview_required=false;

    // viewport
    view[IS_WX]=view[IS_WY]=0.0;
    view[IS_WW]=view[IS_WH]=1.0;
    view_percentage[IS_WX]=view_percentage[IS_WY]=view_percentage[IS_WW]=view_percentage[IS_WH]=true;
    aspect=1;

    // clear color
    clear=true;
    clear_color.set_color(CLEAR_COLOR_NONSELECT);
    render_color_anim::init_color(clear_color_nonselect, CLEAR_COLOR_NONSELECT);
    render_color_anim::init_color(clear_color_select,    CLEAR_COLOR_SELECT);
    render_color_anim::init_color(clear_color_cselect,   CLEAR_COLOR_CSELECT);
    clear_color_anim_usec=CLEAR_COLOR_ANIM_USEC;

    // stats
    stats=NULL;
    render_color_anim::init_color(stats_title_color,     STATS_COLOR_TITLE);
    render_color_anim::init_color(stats_value_color,     STATS_COLOR_VALUE);
    render_color_anim::init_color(stats_box_color,       STATS_COLOR_BOX);
    stats_box_position[IS_WX]=0;
    stats_box_position[IS_WY]=0;

    // cursor
    cursor=true;
    render_color_anim::init_color(cursor_color,          CURSOR_COLOR);

    // help
    help=true;
    help_obj=NULL;
    help_selector=IS_SEL_NONE;
    help_action=EM_ACTION_NONE;
    util::ctor_vector(help_str, n_help_str, max_help_str);
    render_color_anim::init_color(help_tip_color,        HELP_TIP_COLOR);
    render_color_anim::init_color(help_key_color,        HELP_KEY_COLOR);
    render_color_anim::init_color(help_box_color,        HELP_COLOR_BOX);
    help_max_key=help_max_tip=0;
    help_box_precalcs[IS_WXYWH+IS_X]=help_box_precalcs[IS_WXYWH+IS_Y]=0.0; // stores cursor cuadrant
}

void render_obj_frame::dtor()
{
    if(stats) {stats->decref(); stats=NULL;}

    // help
    util::dtor_vector(help_str, n_help_str, max_help_str);
}

bool render_obj_frame::init(render_mng *ctx)
{
    // init stats if required
    if(stats) stats->init();

    // actions
    struct
    {
	unsigned act_type, act_seq, act_coord, act_cursor, act_key;
	unsigned key_code, key_status;
	unsigned obj_id, obj_status;
	unsigned selector;
	const char *tip;
    } act[]=
      {
	  {
	      EM_ACTION_TYPE_INDEX_0, EM_ACTION_SEQ_ONCE, EM_ACTION_COORD_NONE, EM_ACTION_CURSOR_NORMAL, EM_ACTION_KEY_NORMAL,
	      EM_BUTKEY_OFF | IS_BUTTON_LEFT, 0,
	      IS_TYPE_FRAME, IS_STATUS_SELECTED, SEL_FRAME_STATS,
	      "Frame: Stats: Next page"
	  },
	  {
	      EM_ACTION_TYPE_INDEX_1, EM_ACTION_SEQ_ONCE, EM_ACTION_COORD_NONE, EM_ACTION_CURSOR_NORMAL, EM_ACTION_KEY_NORMAL,
	      EM_BUTKEY_OFF | IS_BUTTON_RIGHT, 0,
	      IS_TYPE_FRAME, IS_STATUS_SELECTED, SEL_FRAME_STATS,
	      "Frame: Stats: Previous page"
	  },
	  {
	      EM_ACTION_TYPE_INDEX_0, EM_ACTION_SEQ_ONCE, EM_ACTION_COORD_NONE, EM_ACTION_CURSOR_NORMAL, EM_ACTION_KEY_NORMAL,
	      IS_KN_H, 0,
	      IS_TYPE_FRAME, IS_STATUS_ANY, IS_SEL_NONE,
	      "Frame: Show/Hide help"
	  },
	  {
	      EM_ACTION_TYPE_INDEX_1, EM_ACTION_SEQ_ONCE, EM_ACTION_COORD_NONE, EM_ACTION_CURSOR_NORMAL, EM_ACTION_KEY_NORMAL,
	      IS_KN_H, ShiftMask,
	      IS_TYPE_FRAME, IS_STATUS_ANY, IS_SEL_NONE,
	      "Frame: Show/Hide statistics"
	  },
	  {
	      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL
	  }
      };

    for(int i=0; act[i].tip; i++)
	ctx->get_event()->action_register(
	    EM_SET_ACTION(act[i].act_type, act[i].act_seq, act[i].act_coord, act[i].act_cursor, act[i].act_key),
	    act[i].key_code, act[i].key_status, act[i].obj_id, act[i].obj_status, act[i].selector, act[i].tip);

    return render_obj::init(ctx);
}

void render_obj_frame::render(render_mng *ctx)
{
    render_pass pass=ctx->get_pass();

    // time select pass and enable ctx stats
    if(stats)
    {
	stats->clock(pass);
	stats->inc_pass(pass);
	stats->reset_render_init(pass);
	ctx->set_stats(stats);
    }

    // base render init
    render_init(ctx);

    // save this frame in context
    render_obj_frame *old_frame=ctx->get_obj_frame();
    ctx->set_obj_frame(this);

    if(pass==RENDER_PASS_SELECT)
    {
	// check cursor position
	int cx=ctx->get_event()->get_cursor(IS_WX)-viewport[IS_WX];
	int cy=ctx->get_event()->get_cursor(IS_WY)-viewport[IS_WY];
	if(cx >= 0 && cy >= 0 && cx < viewport[IS_WW] && cy < viewport[IS_WH])
	{
	    // perspective matrix
	    render_perspective_selection(ctx);

	    // common render params
	    render_params_common();

	    // clear frame
	    if(clear) render_clear_select();

	    // select render on children
	    render_children(ctx);

	    // stats
	    if(stats) render_statistics_select(ctx);
	}
    }
    else
    {
	// perspective matrix
	render_perspective();

	// specific
	if(pass==RENDER_PASS_PRECALC)
	{
	    required_pass=1<<RENDER_PASS_OPAQUE | 1<<RENDER_PASS_HUD | 1<<RENDER_PASS_SELECT;
	    render_calculate_viewport(ctx);
	    render_calculate_precalcs(ctx);
	    // this forces whole help recalculation (error with box size in configuration events)
	    if(ctx->get_forced_render()) help_obj=NULL;
	}

	// render params
	render_params_common();

	if(pass==RENDER_PASS_OPAQUE)
	{
	    if(clear) render_clear();
	    render_params_opaque();
	}

	// precalc render on children
	bool forced_render=ctx->get_forced_render();
	ctx->set_forced_render(true);
	render_children(ctx);
	ctx->set_forced_render(forced_render);

	if(pass==RENDER_PASS_HUD)
	{
	    if(ctx->get_pass_hud_aux())
	    {
		glDisable(GL_DEPTH_TEST);
		glDrawBuffer(GL_AUX0);
		glRasterPos2i(-1, -1);
		glCopyPixels(viewport[IS_WX], viewport[IS_WY], viewport[IS_WW], viewport[IS_WH], GL_COLOR);
	    }

	    // gl params for HUD
	    glDisable(GL_DEPTH_TEST);
	    glDisable(GL_LIGHTING);
	    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	    // statistics render
	    if(stats)
	    {
		// the following line commented: 1. take swap buffer time into account  2. shown one frame later
		// stats->clock_stop(pass);
		render_statistics(ctx);
	    }

	    // cursor && help
	    bool show_cursor;
	    unsigned cursor_type=EM_GET_ACTION_CURSOR(ctx->get_focus_action());
	    if((show_cursor=(cursor && cursor_type!=EM_ACTION_CURSOR_INVISIBLE)) || help)
	    {
		int cx=ctx->get_event()->get_cursor(IS_WX)-viewport[IS_WX];
		int cy=ctx->get_event()->get_cursor(IS_WY)-viewport[IS_WY];
		if((cx >= 0 && cy >= 0 && cx < viewport[IS_WW] && cy < viewport[IS_WH]) || help_action || ctx->get_focus_action())
		{
		    if(show_cursor) render_cursor(cx, cy, cursor_type);
		    if(help) render_help(ctx, cx, cy);
		}
	    }

	    // copy from back
	    if(ctx->get_pass_hud_aux()) glReadBuffer(GL_AUX0);
	    glDrawBuffer(GL_FRONT);
	    glRasterPos2i(-1, -1);
	    glCopyPixels(viewport[IS_WX], viewport[IS_WY], viewport[IS_WW], viewport[IS_WH], GL_COLOR);
#if 0
	    glFlush();
#else
	    // this is necesary in some ogl implementations
	    glFinish();
#endif
	    glDrawBuffer(GL_BACK);
	    glReadBuffer(GL_BACK);
	}
    }

    // restore old frame
    ctx->set_obj_frame(old_frame);

    // base render finish
    render_finish(ctx);

    // disable ctx stats
    if(stats)
    {
	ctx->set_stats(NULL);
	stats->clock_stop(pass);
    }
}

void render_obj_frame::render_cursor(int xx, int yy, unsigned type)
{
    glEnable(GL_BLEND);
    glColor4fv(cursor_color);

    if(type==EM_ACTION_CURSOR_NORMAL)
    {
	float x=-1.0 + ((float)xx+0.5)*pixel[IS_WX];
	float y=-1.0 + ((float)yy+0.5)*pixel[IS_WY];

	// cursor
	glBegin(GL_LINES);
	glVertex4f( x, -1, -1, 1);
	glVertex4f( x,  1, -1, 1);
	glVertex4f(-1,  y, -1, 1);
	glVertex4f( 1,  y, -1, 1);
	glEnd();
    }
    else if(type==EM_ACTION_CURSOR_CENTER)
    {
	float x=pixel[IS_WX]*CENTER_CURSOR_SIZE;
	float y=pixel[IS_WY]*CENTER_CURSOR_SIZE;

	// cursor
	glBegin(GL_LINES);
	glVertex4f(-x,  0, -1, 1);
	glVertex4f( x,  0, -1, 1);
	glVertex4f( 0, -y, -1, 1);
	glVertex4f( 0,  y, -1, 1);
	glEnd();
    }

    glDisable(GL_BLEND);
}

void render_obj_frame::render_help(render_mng *ctx, int xx, int yy)
{
    ogl_font *f;
    if((f=ctx->get_font(RENDER_FONT_BITMAP)))
    {
	render_obj *obj=ctx->get_select_hierarchy_top();
	unsigned sel=ctx->get_select_hierarchy_top_selector();
	if(help_obj!=obj || help_selector!=sel || help_action!=ctx->get_focus_action())
	{
	    // update help text
	    help_obj=obj;
	    help_selector=sel;
	    help_action=ctx->get_focus_action();
	    n_help_str=help_max_key=help_max_tip=0;

	    // get action entries
	    event_mng *e=ctx->get_event();
	    int i=ctx->selection_n_hierarchy()-1;
	    while(obj)
	    {
		// get actions for this object and selector
		int entries[MAX_TIP_ENTRIES];
		int n_entries=e->action_possibilities(MAX_TIP_ENTRIES, entries, obj->get_type(), sel, help_action);

		// get actions info
		for(int j=0; j<n_entries; j++)
		{
		    unsigned short key_code, key_status;
		    const char *tip;
		    e->action_info(entries[j], &key_code, &key_status, &tip);
		    if(tip)
		    {
			int k;
			for(k=0; k<n_help_str; k++)
			    if(help_str[k].key_code==key_code && help_str[k].key_status==key_status)
				break;
			if(k<n_help_str) continue; // repeated key stroke
			k=util::inc_vector(help_str, n_help_str, max_help_str, INC_HELP_STR);
			help_str[k].key_code=key_code;
			help_str[k].key_status=key_status;
			help_str[k].tip=tip;
			e->key_to_string(key_code, key_status, help_str[k].key_name, ROF_MAX_KEYNAME);

			// maximum string sizes
			if((k=strlen(help_str[k].key_name))>help_max_key) help_max_key=k;
			if((k=strlen(tip))>help_max_tip) help_max_tip=k;
		    }
		}

		// get next object and selector
		if(--i<0) break;
		obj=ctx->selection_hierarchy(i);
		unsigned *params;
		if(!ctx->selection_obj_params(obj, &params) || (params[0]&IS_GL_NAME_PARAM)!=IS_GL_NAME_SELECTOR)
		    sel=IS_SEL_NONE;
		else
		    sel=params[0];
	    }

	    // calculate help box size
	    help_box_precalcs[IS_WX]=-1.0*pixel[IS_WX];
	    help_box_precalcs[IS_WY]=-2.0*pixel[IS_WY];
	    help_box_precalcs[IS_WW]=(help_max_key+help_max_tip+1.5)*f->get_width()*pixel[IS_WX];
	    help_box_precalcs[IS_WH]=(n_help_str*f->get_height())*pixel[IS_WY];

	    // quadrant
	    if(help_box_precalcs[IS_WXYWH+IS_X]<0)
		help_box_precalcs[IS_WXYWH+IS_X]=-HELP_BOX_MARGX*pixel[IS_WX]-help_box_precalcs[IS_WW];
	    if(help_box_precalcs[IS_WXYWH+IS_Y]<0)
		help_box_precalcs[IS_WXYWH+IS_Y]=-HELP_BOX_MARGY*pixel[IS_WY]-help_box_precalcs[IS_WH];
	}

	if(n_help_str)
	{
	    if(help_action) {xx=help_coord[IS_X]; yy=help_coord[IS_Y];}
	    else {help_coord[IS_X]=xx; help_coord[IS_Y]=yy;}
	    float x1=-1.0 + ((float)xx+HELP_BOX_MARGX)*pixel[IS_WX];
	    float x2=-1.0 + ((float)xx+HELP_BOX_MARGX+(help_max_key+1.5)*f->get_width())*pixel[IS_WX];
	    float y= -1.0 + ((float)yy+HELP_BOX_MARGY)*pixel[IS_WY];

	    // move box to preferred quadrant
	    if(x1>0 && x1+help_box_precalcs[IS_WW]+help_box_precalcs[IS_WXYWH+IS_X]>=1.0)
		help_box_precalcs[IS_WXYWH+IS_X]=-HELP_BOX_MARGX*pixel[IS_WX]-help_box_precalcs[IS_WW];
	    if(x1+help_box_precalcs[IS_WXYWH+IS_X]<=-1.0)
		help_box_precalcs[IS_WXYWH+IS_X]=0.0;
	    if(y>0 && y-2.0*pixel[IS_WY]+help_box_precalcs[IS_WH]+help_box_precalcs[IS_WXYWH+IS_Y]>=1.0)
		help_box_precalcs[IS_WXYWH+IS_Y]=-HELP_BOX_MARGY*pixel[IS_WY]-help_box_precalcs[IS_WH];
	    if(y-2.0*pixel[IS_WY]+help_box_precalcs[IS_WXYWH+IS_Y]<=-1.0)
		help_box_precalcs[IS_WXYWH+IS_Y]=0.0;

	    // update coordinates to quadrant
	    x1+=help_box_precalcs[IS_WXYWH+IS_X];
	    x2+=help_box_precalcs[IS_WXYWH+IS_X];
	    y+= help_box_precalcs[IS_WXYWH+IS_Y];

	    // surrounding transparent box
	    glEnable(GL_BLEND);
	    glColor4fv(help_box_color);
	    glBegin(GL_QUADS);
	    glVertex4f(x1+help_box_precalcs[IS_WX], y+help_box_precalcs[IS_WY], 1, 1);
	    glVertex4f(x1+help_box_precalcs[IS_WW], y+help_box_precalcs[IS_WY], 1, 1);
	    glVertex4f(x1+help_box_precalcs[IS_WW], y+help_box_precalcs[IS_WH], 1, 1);
	    glVertex4f(x1+help_box_precalcs[IS_WX], y+help_box_precalcs[IS_WH], 1, 1);
	    glEnd();
	    glDisable(GL_BLEND);
	    // text
	    for(int i=0; i<n_help_str; i++)
	    {
		// key
		glColor4fv(help_key_color);
		glRasterPos2f(x1, y);
		f->write("%s", help_str[i].key_name);
		// tip
		glColor4fv(help_tip_color);
		glRasterPos2f(x2, y);
		f->write("%s", help_str[i].tip);
		// next line
		y+=pixel[IS_WY]*f->get_height();
	    }
	}
    }
}

void render_obj_frame::render_calculate_viewport(render_mng *ctx)
{
    int w[2];
    w[0]=ctx->get_window()->get_width();
    w[1]=ctx->get_window()->get_height();
    for(int i=0; i<IS_WXYWH; i++)
	viewport[i]=view_percentage[i]?
	    (int)((float)view[i]*(float)w[i&1]+0.5):
	    (int)(view[i]+0.5);
    aspect=(double)viewport[IS_WW]/(double)viewport[IS_WH];
}

void render_obj_frame::render_perspective()
{
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
}

void render_obj_frame::render_perspective_selection(render_mng *ctx)
{
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPickMatrix(
	ctx->get_event()->get_cursor(IS_WX),
	ctx->get_event()->get_cursor(IS_WY), 1.0, 1.0, viewport);
}

void render_obj_frame::render_params_common()
{
    // set viewport and reset matrices
    glViewport(viewport[IS_WX], viewport[IS_WY], viewport[IS_WW], viewport[IS_WH]);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    // render parameters
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);
    glPolygonMode(GL_FRONT, GL_FILL);
}

void render_obj_frame::render_clear()
{
    glDisable(GL_LIGHTING);
    unsigned char color_tmp[IS_RGBA];
    if(clear_color.refresh(color_tmp)) forced_pass=RENDER_PASS_OPAQUE; // require refresh
    glColor4ubv(color_tmp);
    glDepthFunc(GL_ALWAYS); // clear depth buffer
    glDisable(GL_POLYGON_OFFSET_FILL);
    glBegin(GL_QUADS);
    glVertex4f(-1, -1, 1, 1); glVertex4f( 1, -1, 1, 1);
    glVertex4f( 1,  1, 1, 1); glVertex4f(-1,  1, 1, 1);
    glEnd();
    glEnable(GL_POLYGON_OFFSET_FILL);
    glDepthFunc(GL_LESS);
}

void render_obj_frame::render_clear_select()
{
    glDepthFunc(GL_ALWAYS); // clear depth buffer
    glDisable(GL_POLYGON_OFFSET_FILL);
    glBegin(GL_QUADS);
    glVertex4f(-1, -1, 1, 1); glVertex4f( 1, -1, 1, 1);
    glVertex4f( 1,  1, 1, 1); glVertex4f(-1,  1, 1, 1);
    glEnd();
    glEnable(GL_POLYGON_OFFSET_FILL);
    glDepthFunc(GL_LESS);
}

void render_obj_frame::render_params_opaque()
{
    // light
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    float ambient[4]={0.4, 0.4, 0.4, 1.0};
    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    float position[4]={0, 0, 0, 1};
    glLightfv(GL_LIGHT0, GL_POSITION, position);
}

void render_obj_frame::render_calculate_precalcs(render_mng *ctx)
{
    // coordinates
    pixel[IS_WX]=2.0/(float)viewport[IS_WW];
    pixel[IS_WY]=2.0/(float)viewport[IS_WH];

    ogl_font *f;
    if(stats && (f=ctx->get_font(RENDER_FONT_BITMAP)))
    {
	float width=f->get_width();
	float height=f->get_height();

	// box
	stat_box_precalcs[IS_WW]=
	    (2*STATS_BOX_MARGIN+STATS_BOX_NCOLS*width)*pixel[IS_WX];

	stat_box_precalcs[IS_WX]=
	    -1.0 + stats_box_position[IS_WX]*pixel[IS_WX];

	stat_box_precalcs[IS_WH]=
	    (2*STATS_BOX_MARGIN+STATS_BOX_TITLE_MARGIN+STATS_BOX_NLINES*height)*pixel[IS_WY];

	stat_box_precalcs[IS_WY]=
	    -1.0 + stats_box_position[IS_WY]*pixel[IS_WY];

	// justifications
	if(stats_box_position[IS_WX]<STATS_BOX_NEG_MARGIN)
	    stat_box_precalcs[IS_WX]=1.0 - stat_box_precalcs[IS_WW];
	if(stats_box_position[IS_WY]<STATS_BOX_NEG_MARGIN)
	    stat_box_precalcs[IS_WY]=1.0 - stat_box_precalcs[IS_WH];

	stat_box_precalcs[IS_WW]+=stat_box_precalcs[IS_WX];
	stat_box_precalcs[IS_WH]+=stat_box_precalcs[IS_WY];

	// columns
	stat_box_precalcs[IS_WXYWH]=stat_box_precalcs[IS_WX]+STATS_BOX_MARGIN*pixel[IS_WX];
	stat_box_precalcs[IS_WXYWH+1]=stat_box_precalcs[IS_WXYWH] + STATS_BOX_NCOLS2*width*pixel[IS_WX];

	// rows
	stat_box_precalcs[IS_WXYWH+2]=stat_box_precalcs[IS_WH]-(STATS_BOX_MARGIN+height-STATS_BOX_YOFF)*pixel[IS_WY];
	stat_box_precalcs[IS_WXYWH+3]=height*pixel[IS_WY];
    }
}

void render_obj_frame::render_statistics_select(render_mng *ctx)
{
    if(ctx->get_font(RENDER_FONT_BITMAP))
    {
	glPushName(SEL_FRAME_STATS);

	// gl params for raster fonts
	glDisable(GL_DEPTH_TEST);

	// surrounding transparent box
	glBegin(GL_QUADS);
	glVertex4f(stat_box_precalcs[IS_WX], stat_box_precalcs[IS_WY], -1, 1);
	glVertex4f(stat_box_precalcs[IS_WW], stat_box_precalcs[IS_WY], -1, 1);
	glVertex4f(stat_box_precalcs[IS_WW], stat_box_precalcs[IS_WH], -1, 1);
	glVertex4f(stat_box_precalcs[IS_WX], stat_box_precalcs[IS_WH], -1, 1);
	glEnd();

	glPopName();
    }
}

void render_obj_frame::render_statistics(render_mng *ctx)
{
    ogl_font *f;
    if((f=ctx->get_font(RENDER_FONT_BITMAP)))
    {
	// surrounding transparent box
	glEnable(GL_BLEND);
	glColor4fv(stats_box_color);
	glBegin(GL_QUADS);
	glVertex4f(stat_box_precalcs[IS_WX], stat_box_precalcs[IS_WY], 1, 1);
	glVertex4f(stat_box_precalcs[IS_WW], stat_box_precalcs[IS_WY], 1, 1);
	glVertex4f(stat_box_precalcs[IS_WW], stat_box_precalcs[IS_WH], 1, 1);
	glVertex4f(stat_box_precalcs[IS_WX], stat_box_precalcs[IS_WH], 1, 1);
	glEnd();

	// y coord
	float y=stat_box_precalcs[IS_WXYWH+2];

	// title
	glDisable(GL_BLEND);
	glColor4fv(stats_title_color);
	glRasterPos2f(stat_box_precalcs[IS_WXYWH], y);
	f->write("%7s", "LAYER");
	glColor4fv(stats_value_color);
	glRasterPos2f(stat_box_precalcs[IS_WXYWH+1], y);
	f->write("%9s", stats->get_current_stat_str());
	y-=stat_box_precalcs[IS_WXYWH+3] + STATS_BOX_TITLE_MARGIN*pixel[IS_WY];

	// write stats
	for(int i=0; i<RENDER_PASS_LAST_ALL; i++)
	{
	    char buffer[RS_MAX_BUFFER];
	    if(i==RENDER_PASS_LAST) continue;

	    glColor4fv(stats_title_color);
	    glRasterPos2f(stat_box_precalcs[IS_WXYWH], y);
	    f->write("%7s", render_pass_name[i]);
	    glColor4fv(stats_value_color);
	    glRasterPos2f(stat_box_precalcs[IS_WXYWH+1], y);
	    f->write("%9s", stats->get_current_stat((render_pass)i, buffer));
	    y-=stat_box_precalcs[IS_WXYWH+3];
	}
    }
}

bool render_obj_frame::event(render_mng *ctx)
{
    render_obj::event(ctx);
    if(ctx->get_event_last())
    {
	// status event: IS_STATUS_*
	if(status & IS_STATUS_SELECTED)
	    clear_color.set_color_dest(clear_color_select, clear_color_anim_usec);
	else if(status & IS_STATUS_CSELECTED)
	    clear_color.set_color_dest(clear_color_cselect, clear_color_anim_usec);
	else
	    clear_color.set_color_dest(clear_color_nonselect, clear_color_anim_usec);

	// force refresh
	ctx->set_forced_pass(RENDER_PASS_OPAQUE, this);
    }
    else
    {
	event_mng *e=ctx->get_event();
	unsigned action;
	if((action=e->get_action_last()))
	    return action_dispatcher(ctx, EM_GET_ACTION_TYPE(action), EM_GET_ACTION_EVENT(action));

	// keyboard or mouse event
	if(e->get_event_last()==OGL_MOUSE_MOVE_EVENT)
	{
	    ctx->set_forced_pass(RENDER_PASS_HUD, this);
	}
	else if(e->get_event_last()==OGL_ENTER_LEAVE_EVENT)
	{
	    // this forced pass is to remove cursor when pointer is out of the window (not the frame)
	    if(!e->get_cursor_in()) ctx->set_forced_pass(RENDER_PASS_OPAQUE, this);
	}
    }
    return false; // not managed
}

void render_obj_frame::stats_activate()
{
    if(!stats)
    {
	stats=new render_stats;
	stats->init();
    }
}

void render_obj_frame::stats_deactivate()
{
    if(stats)
    {
	stats->decref();
	stats=NULL;
    }
}

unsigned render_obj_frame::get_type()
{
    return IS_TYPE_FRAME;
}

bool render_obj_frame::action_dispatcher(render_mng *ctx, unsigned type, unsigned event)
{
    event_mng *e=ctx->get_event();
    unsigned selector=e->get_action_last_selector();
    switch(selector)
    {
	case IS_SEL_NONE:
	    switch(type)
	    {
		case EM_ACTION_TYPE_INDEX_0: // help
		    help^=1;
		    ctx->set_forced_pass(RENDER_PASS_HUD, this);
		    return true;
		case EM_ACTION_TYPE_INDEX_1: // statistics
		    if(stats) stats_deactivate();
		    else
		    {
			stats_activate();
			render_calculate_viewport(ctx);
			render_calculate_precalcs(ctx);
		    }
		    ctx->set_forced_pass(RENDER_PASS_HUD, this);
		    return true;
	    }
	    break;

	case SEL_FRAME_STATS:
	    switch(type)
	    {
		case EM_ACTION_TYPE_INDEX_0: // inc stats page
		    if(stats)
		    {
			stats->inc_stats_type();
			ctx->set_forced_pass(RENDER_PASS_HUD, this);
			return true;
		    }
		    break;
		case EM_ACTION_TYPE_INDEX_1: // dec stats page
		    if(stats)
		    {
			stats->dec_stats_type();
			ctx->set_forced_pass(RENDER_PASS_HUD, this);
			return true;
		    }
		    break;
	    }
	    break;
    }
    return false;
}
